package cn.chiship.sdk.third.service;

import cn.chiship.sdk.cache.service.RedisService;
import cn.chiship.sdk.core.base.BaseResult;
import cn.chiship.sdk.core.base.constants.BaseConstants;
import cn.chiship.sdk.core.encryption.Sha1Util;
import cn.chiship.sdk.core.exception.ExceptionUtil;
import cn.chiship.sdk.core.exception.custom.BusinessException;
import cn.chiship.sdk.core.util.*;
import cn.chiship.sdk.core.util.http.HttpUtil;
import cn.chiship.sdk.core.util.http.ResponseUtil;
import cn.chiship.sdk.third.core.common.ThirdConstants;
import cn.chiship.sdk.third.core.wx.enums.WeiXinLanguageEnum;
import cn.chiship.sdk.third.core.wx.enums.WxPubMaterialTypeEnum;
import cn.chiship.sdk.third.core.model.WeiXinConfigModel;

import cn.chiship.sdk.third.core.wx.pub.entity.*;
import cn.chiship.sdk.third.core.wx.pub.entity.message.TemplateMessage;
import cn.chiship.sdk.third.core.wx.util.WxPubUtil;
import cn.chiship.sdk.third.properties.ChishipWxPubProperties;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.ObjectUtils;
import org.dom4j.DocumentException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.net.ssl.HttpsURLConnection;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * 微信公众号相关业务实现
 * URL:https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Overview.html
 *
 * @author lijian
 */
@Component
public class WxPubService {

    protected static final Logger LOGGER = LoggerFactory.getLogger(WxPubService.class);

    public static final String API_WEI_XIN_SERVER_HOST = "https://api.weixin.qq.com/";

    public static final String MP_WEI_XIN_SERVER_HOST = "https://mp.weixin.qq.com/";

    public static final String OPEN_WEI_XIN_SERVER_HOST = "https://open.weixin.qq.com/";

    @Resource
    ChishipWxPubProperties chishipWxPubProperties;

    @Resource
    RedisService redisService;

    private static final String TAG_ID = "tagid";

    private static final String OPEN_ID = "openid";

    private static final String APP_ID = "appid";

    private static final String ACCESS_TOKEN = "access_token";

    private WeiXinConfigModel weiXinConfigModel;

    private String accessToken = null;

    public WxPubService config() {
        this.weiXinConfigModel = new WeiXinConfigModel(chishipWxPubProperties.getAppKey(),
                chishipWxPubProperties.getAppSecret());
        return this;
    }

    public WxPubService config(WeiXinConfigModel weiXinConfigModel) {
        if (ObjectUtils.isEmpty(weiXinConfigModel)) {
            LOGGER.info("微信公众号实例配置为空，将自动加载默认配置信息");
            return config();
        }
        this.weiXinConfigModel = weiXinConfigModel;
        return this;
    }

    public WxPubService token() {
        BaseResult baseResult = getToken();
        if (!baseResult.isSuccess()) {
            throw new BusinessException(baseResult.getData() + "-" + baseResult.getMessage());
        }
        this.accessToken = baseResult.getData().toString();
        return this;
    }

    /**
     * 获得accessToken
     * URL:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
     * 1.access_token是公众号的全局唯一接口调用凭据，公众号调用各接口时都需使用access_token。 2.开发者需要进行妥善保存。
     * 3.access_token的存储至少要保留512个字符空间。
     * 4.access_token的有效期目前为2个小时，需定时刷新，重复获取将导致上次获取的access_token失效。
     *
     * @return 结果 返回token
     */
    public BaseResult getToken() {
        try {
            String getTokenUrl = API_WEI_XIN_SERVER_HOST + "cgi-bin/token";
            String key = ThirdConstants.REDIS_WEIXIN_PUB_ACCESS_TOKEN + ":" + this.weiXinConfigModel.getAppKey();
            String token = StringUtil.getString(redisService.get(key), null);
            if (!StringUtil.isNullOrEmpty(token)) {
                return BaseResult.ok(token);
            }
            Map<String, Object> query = new HashMap<>(7);
            query.put("grant_type", "client_credential");
            query.put(APP_ID, this.weiXinConfigModel.getAppKey());
            query.put("secret", this.weiXinConfigModel.getAppSecret());
            BaseResult baseResult = HttpUtil.getInstance().doGet(getTokenUrl, query);
            baseResult = analysisHttpResponse(baseResult);
            if (!baseResult.isSuccess()) {
                return baseResult;
            }
            JSONObject dataJson = (JSONObject) baseResult.getData();
            token = dataJson.getString(ACCESS_TOKEN);
            long expiresIn = Long.parseLong(dataJson.getString("expires_in"));
            baseResult.setData(token);
            // 提前5分钟过期
            redisService.set(key, token, expiresIn - 5 * 60);
            return baseResult;
        } catch (Exception e) {
            return ExceptionUtil.formatException(e);
        }
    }

    /**
     * 获取用户基本信息
     * URL:https://developers.weixin.qq.com/doc/offiaccount/User_Management/Get_users_basic_information_UnionID.html#UinonId
     * 1.在关注者与公众号产生消息交互后，公众号可获得关注者的OpenID（加密后的微信号，每个用户对每个公众号的OpenID是唯一的。对于不同公众号，同一用户的openid不同）。
     * 2.公众号可通过本接口来根据OpenID获取用户基本信息，包括语言和关注时间。
     *
     * @param openId 用户标识
     * @return 结果
     */
    public BaseResult getUserInfo(String openId) {
        return getUserInfo(openId, WeiXinLanguageEnum.WX_ZH_CN);
    }

    /**
     * 获得用户基本信息
     * URL:https://developers.weixin.qq.com/doc/offiaccount/User_Management/Get_users_basic_information_UnionID.html#UinonId
     *
     * @param openId             用户标识
     * @param weiXinLanguageEnum 语言
     * @return 结果 Example
     * {"code":200,"data":{"country":"","qr_scene":0,"subscribe":1,"city":"","openid":"ofZpz1jrzEybPkb_JMYcpT_aoSgs","tagid_list":[101,2,108],"sex":0,"groupid":101,"language":"zh_CN","remark":"??","subscribe_time":1629545749,"province":"","subscribe_scene":"ADD_SCENE_QR_CODE","nickname":"","headimgurl":"","qr_scene_str":""},"message":"操作成功","success":true}
     */
    public BaseResult getUserInfo(String openId, WeiXinLanguageEnum weiXinLanguageEnum) {

        try {
            String getUserInfoUrl = API_WEI_XIN_SERVER_HOST + "cgi-bin/user/info";
            Map<String, Object> query = new HashMap<>(7);
            query.put(ACCESS_TOKEN, getAccessToken());
            query.put(OPEN_ID, openId);
            query.put("lang", weiXinLanguageEnum.getLang());
            BaseResult baseResult = HttpUtil.getInstance().doGet(getUserInfoUrl, query);
            return analysisHttpResponse(baseResult);
        } catch (Exception e) {
            return ExceptionUtil.formatException(e);

        }
    }

    /**
     * 获得微信标签
     * URL:https://developers.weixin.qq.com/doc/offiaccount/User_Management/User_Tag_Management.html
     *
     * @return 结果 Example
     * {"code":200,"data":{"tags":[{"name":"星标组","count":0,"id":2}]},"success":true,"message":"操作成功"}
     */
    public BaseResult getTags() {

        try {
            String getTagUrl = API_WEI_XIN_SERVER_HOST + "cgi-bin/tags/get";
            Map<String, Object> query = new HashMap<>(7);
            query.put(ACCESS_TOKEN, getAccessToken());
            BaseResult baseResult = HttpUtil.getInstance().doGet(getTagUrl, query);
            return analysisHttpResponse(baseResult);
        } catch (Exception e) {
            return ExceptionUtil.formatException(e);
        }
    }

    /**
     * 创建微信用户标签
     *
     * @param tagName 标签名称
     * @return 结果 Example
     * {"code":200,"data":{"tag":{"name":"特别关注","id":100}},"success":true,"message":"操作成功"}
     */
    public BaseResult createTag(String tagName) {
        try {
            String createTagUrl = API_WEI_XIN_SERVER_HOST + "cgi-bin/tags/create";
            Map<String, Object> query = new HashMap<>(7);
            query.put(ACCESS_TOKEN, getAccessToken());
            Map<String, Map<String, String>> body = new HashMap<>(7);
            Map<String, String> tag = new HashMap<>(7);
            tag.put("name", tagName);
            body.put("tag", tag);
            BaseResult baseResult = HttpUtil.getInstance().doPost(createTagUrl, query, JSON.toJSONString(body));
            return analysisHttpResponse(baseResult);
        } catch (Exception e) {
            return ExceptionUtil.formatException(e);
        }
    }

    /**
     * 编辑用户标签
     *
     * @param tagId   标签ID
     * @param tagName 标签名称
     * @return 结果 Example
     * {"code":200,"data":{"errcode":0,"errmsg":"ok"},"success":true,"message":"操作成功"}
     */
    public BaseResult updateTag(String tagId, String tagName) {
        try {
            String updateTagUrl = API_WEI_XIN_SERVER_HOST + "cgi-bin/tags/update";
            Map<String, Object> query = new HashMap<>(7);
            query.put(ACCESS_TOKEN, getAccessToken());
            Map<String, Map<String, String>> body = new HashMap<>(7);
            Map<String, String> tag = new HashMap<>(7);
            tag.put("name", tagName);
            tag.put("id", tagId);
            body.put("tag", tag);
            BaseResult baseResult = HttpUtil.getInstance().doPost(updateTagUrl, query, JSON.toJSONString(body));
            return analysisHttpResponse(baseResult);
        } catch (Exception e) {
            return ExceptionUtil.formatException(e);
        }
    }

    /**
     * 删除用户标签
     *
     * @param tagId 标签ID
     * @return 结果 Example
     * {"code":200,"data":{"errcode":0,"errmsg":"ok"},"success":true,"message":"操作成功"}
     */
    public BaseResult deleteTag(String tagId) {
        try {
            String deleteTagUrl = API_WEI_XIN_SERVER_HOST + "cgi-bin/tags/delete";
            Map<String, Object> query = new HashMap<>(7);
            query.put(ACCESS_TOKEN, getAccessToken());
            Map<String, Map<String, String>> body = new HashMap<>(7);
            Map<String, String> tag = new HashMap<>(7);
            tag.put("id", tagId);
            body.put("tag", tag);
            BaseResult baseResult = HttpUtil.getInstance().doPost(deleteTagUrl, query, JSON.toJSONString(body));
            return analysisHttpResponse(baseResult);
        } catch (Exception e) {
            return ExceptionUtil.formatException(e);
        }
    }

    /**
     * 获得标签下用户
     *
     * @param tagId 标签ID
     * @return 结果 Example
     * {"code":200,"data":{"data":{"openid":["ofZpz1tFF7Ek0C5ci4VJ398upvEo"]},"count":1,"next_openid":"ofZpz1tFF7Ek0C5ci4VJ398upvEo"},"success":true,"message":"操作成功"}
     */
    public BaseResult tagGetUser(String tagId) {
        return tagGetUser(tagId, null);
    }

    /**
     * 获得标签下用户
     *
     * @param tagId      标签ID
     * @param nextOpenId 下一个用户标识
     * @return 结果 Example
     * {"code":200,"data":{"data":{"openid":["ofZpz1jrzEybPkb_JMYcpT_aoSgs"]},"count":1,"next_openid":"ofZpz1jrzEybPkb_JMYcpT_aoSgs"},"message":"操作成功","success":true}
     */
    public BaseResult tagGetUser(String tagId, String nextOpenId) {
        try {
            String tagGetUserUrl = API_WEI_XIN_SERVER_HOST + "cgi-bin/user/tag/get";
            if (StringUtil.isNull(nextOpenId)) {
                nextOpenId = "";
            }
            Map<String, Object> query = new HashMap<>(7);
            query.put(ACCESS_TOKEN, getAccessToken());
            Map<String, Object> body = new HashMap<>(7);
            body.put(TAG_ID, tagId);
            body.put("next_openid", nextOpenId);
            BaseResult baseResult = HttpUtil.getInstance().doPost(tagGetUserUrl, query, body);
            return analysisHttpResponse(baseResult);
        } catch (Exception e) {
            return ExceptionUtil.formatException(e);
        }
    }

    /**
     * 用户绑定标签
     *
     * @param openId 用户标识
     * @param tagId  标签ID
     * @return 结果
     */
    public BaseResult memberBatchTag(String openId, String tagId) {
        return memberBatchTag(Arrays.asList(openId), tagId);
    }

    /**
     * 用户绑定标签
     *
     * @param openIds 用户集合
     * @param tagId   标签ID
     * @return 结果
     */
    public BaseResult memberBatchTag(List<String> openIds, String tagId) {
        try {
            if (openIds.isEmpty()) {
                return BaseResult.error("用户集合不能为空!");
            }
            String memberBatchTagUrl = API_WEI_XIN_SERVER_HOST + "cgi-bin/tags/members/batchtagging";
            Map<String, Object> query = new HashMap<>(7);
            query.put(ACCESS_TOKEN, getAccessToken());
            Map<String, Object> body = new HashMap<>(7);
            body.put(TAG_ID, tagId);
            body.put("openid_list", openIds);
            BaseResult baseResult = HttpUtil.getInstance().doPost(memberBatchTagUrl, query, body);
            return analysisHttpResponse(baseResult);
        } catch (Exception e) {
            return ExceptionUtil.formatException(e);
        }
    }

    /**
     * 用户取消绑定标签
     *
     * @param openId 用户标识
     * @param tagId  标签ID
     * @return 结果
     */
    public BaseResult memberBatchUnTag(String openId, String tagId) {
        return memberBatchUnTag(Arrays.asList(openId), tagId);
    }

    /**
     * 用户取消绑定标签
     *
     * @param openIds 用户集合
     * @param tagId   标签ID
     * @return 结果
     */
    public BaseResult memberBatchUnTag(List<String> openIds, String tagId) {
        try {
            if (openIds.isEmpty()) {
                return BaseResult.error("用户集合不能为空!");
            }
            String memberBatchUnTagUrl = API_WEI_XIN_SERVER_HOST + "cgi-bin/tags/members/batchuntagging";
            Map<String, Object> query = new HashMap<>(7);
            query.put(ACCESS_TOKEN, getAccessToken());
            Map<String, Object> body = new HashMap<>(7);
            body.put(TAG_ID, tagId);
            body.put("openid_list", openIds);
            BaseResult baseResult = HttpUtil.getInstance().doPost(memberBatchUnTagUrl, query, body);
            return analysisHttpResponse(baseResult);
        } catch (Exception e) {
            return ExceptionUtil.formatException(e);
        }
    }

    /**
     * 设置用户备注名
     * URL:https://developers.weixin.qq.com/doc/offiaccount/User_Management/Configuring_user_notes.html
     *
     * @param openId
     * @param remark 新的备注名，长度必须小于30字节，大于30个字节，将会自动截取,自动截取有可能会出现乱码
     * @return 结果
     */
    public BaseResult updateUserRemark(String openId, String remark) {
        String updateRemarkUrl = API_WEI_XIN_SERVER_HOST + "cgi-bin/user/info/updateremark";
        int remarkLength = 30;
        byte[] bytes = remark.getBytes(StandardCharsets.UTF_8);
        if (bytes.length > remarkLength) {
            bytes = Arrays.copyOfRange(bytes, 0, 30);
            remark = new String(bytes);
        }
        Map<String, Object> query = new HashMap<>(7);
        query.put(ACCESS_TOKEN, getAccessToken());

        Map<String, Object> body = new HashMap<>(7);
        body.put(OPEN_ID, openId);
        body.put("remark", remark);
        try {
            BaseResult baseResult = HttpUtil.getInstance().doPost(updateRemarkUrl, query, body);
            return analysisHttpResponse(baseResult);
        } catch (Exception e) {
            return ExceptionUtil.formatException(e);

        }
    }

    /**
     * 获取用户列表
     * URL:https://developers.weixin.qq.com/doc/offiaccount/User_Management/Getting_a_User_List.html
     * 第一个拉取的OPENID，不填默认从头开始拉取
     * 1.公众号可通过本接口来获取帐号的关注者列表，关注者列表由一串OpenID（加密后的微信号，每个用户对每个公众号的OpenID是唯一的）组成。
     * 2.一次拉取调用最多拉取10000个关注者的OpenID，可以通过多次拉取的方式来满足需求。
     *
     * @return 结果
     */
    public BaseResult syncUser() {
        return syncUser(null);
    }

    public BaseResult syncUser(String nextOpenId) {
        try {
            String getUserUrl = API_WEI_XIN_SERVER_HOST + "cgi-bin/user/get";
            Map<String, Object> query = new HashMap<>(7);
            query.put(ACCESS_TOKEN, getAccessToken());
            query.put("next_openid", nextOpenId);
            BaseResult baseResult = HttpUtil.getInstance().doGet(getUserUrl, query);
            return analysisHttpResponse(baseResult);
        } catch (Exception e) {
            return ExceptionUtil.formatException(e);
        }
    }

    /**
     * 创建自定义菜单
     * URL:https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Creating_Custom-Defined_Menu.html
     * 1.自定义菜单最多包括3个一级菜单，每个一级菜单最多包含5个二级菜单。 2.一级菜单最多4个汉字，二级菜单最多8个汉字，多出来的部分将会以“...”代替。
     * 3.创建自定义菜单后，菜单的刷新策略是，在用户进入公众号会话页或公众号profile页时，如果发现上一次拉取菜单的请求在5分钟以前，就会拉取一下菜单，如果菜单有更新，就会刷新客户端的菜单。测试时可以尝试取消关注公众账号后再次关注，则可以看到创建后的效果。​
     *
     * @param wxPubMenu 菜单实体
     * @return 结果
     */
    public BaseResult createMenu(WxPubMenu wxPubMenu) {
        int menuLength = 3;
        if (wxPubMenu.getWxPubButton().size() > menuLength) {
            return BaseResult.error("一级按钮个数最多3个");
        }
        Map<String, Object> query = new HashMap<>(7);
        query.put(ACCESS_TOKEN, getAccessToken());
        String createMenuUrl = API_WEI_XIN_SERVER_HOST + "cgi-bin/menu/create";
        try {
            BaseResult baseResult = HttpUtil.getInstance().doPost(createMenuUrl, query, JSON.toJSONString(wxPubMenu));
            return analysisHttpResponse(baseResult);
        } catch (Exception e) {
            return ExceptionUtil.formatException(e);
        }
    }

    /**
     * 获取菜单
     * url：https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Querying_Custom_Menus.html
     *
     * @return BaseResult
     */
    public BaseResult getMenu() {
        try {
            String getMenuUrl = API_WEI_XIN_SERVER_HOST + "cgi-bin/get_current_selfmenu_info";
            Map<String, Object> query = new HashMap<>(7);
            query.put(ACCESS_TOKEN, getAccessToken());
            BaseResult baseResult = HttpUtil.getInstance().doGet(getMenuUrl, query);
            return analysisHttpResponse(baseResult);
        } catch (Exception e) {
            return ExceptionUtil.formatException(e);
        }
    }

    /**
     * 永久二维码
     * URL:https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html
     *
     * @param sceneStr 场景 长度1-64
     * @return 结果
     */
    public BaseResult getQrCodeByForever(String sceneStr) {
        String params = "{\"action_name\": \"QR_LIMIT_STR_SCENE\", \"action_info\": {\"scene\": {\"scene_str\": \""
                + sceneStr + "\"}}}";
        return getQrCode(params);
    }

    /**
     * 临时二维码
     *
     * @param expireSeconds 单位s 二维码有效时间 二维码生成后的30天（即2592000秒）后过期;若超过30天自动设置30天
     * @param sceneStr      场景 长度1-64
     * @return 结果
     */
    public BaseResult getQrCodeByTemporary(Integer expireSeconds, String sceneStr) {
        int maxExpireSeconds = 2592000;
        if (expireSeconds > maxExpireSeconds) {
            expireSeconds = maxExpireSeconds;
        }
        String params = "{\"expire_seconds\": " + expireSeconds
                + ", \"action_name\": \"QR_STR_SCENE\", \"action_info\": {\"scene\": {\"scene_str\": \"" + sceneStr
                + "\"}}}";
        return getQrCode(params);
    }

    /**
     * 获取二维码
     *
     * @param params
     * @return
     */
    private BaseResult getQrCode(String params) {
        try {
            String getQrCodeTicketUrl = API_WEI_XIN_SERVER_HOST + "cgi-bin/qrcode/create";
            Map<String, Object> query = new HashMap<>(7);
            query.put(ACCESS_TOKEN, getAccessToken());
            BaseResult baseResult = HttpUtil.getInstance().doPost(getQrCodeTicketUrl, query, params);
            BaseResult ticketResult = analysisHttpResponse(baseResult);
            if (!ticketResult.isSuccess()) {
                ticketResult.setData("获取Ticket报错，" + ticketResult.getData());
                return ticketResult;
            }
            String ticket = JSON.parseObject(JSON.toJSONString(ticketResult.getData())).getString("ticket");
            String qrCodeUrl = String.format(MP_WEI_XIN_SERVER_HOST + "cgi-bin/showqrcode" + "?ticket=%s", ticket);
            return BaseResult.ok(ImageUtil.getImgBase64ByUrl(qrCodeUrl));
        } catch (Exception e) {
            return ExceptionUtil.formatException(e);
        }
    }

    /**
     * 设置所属行业
     * URL:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Template_Message_Interface.html
     * 设置行业可在微信公众平台后台完成，每月可修改行业1次，账号仅可使用所属行业中相关的模板，为方便第三方开发者，提供通过接口调用的方式来修改账号所属行业
     *
     * @return 结果
     */
    public BaseResult templateSetIndustry(String industry1, String industry2) {
        try {
            String templateSetIndustryUrl = API_WEI_XIN_SERVER_HOST + "cgi-bin/template/api_set_industry";
            Map<String, Object> query = new HashMap<>(2);
            query.put(ACCESS_TOKEN, getAccessToken());
            Map<String, Object> body = new HashMap<>(2);
            body.put("industry_id1", industry1);
            body.put("industry_id2", industry2);
            BaseResult baseResult = HttpUtil.getInstance().doPost(templateSetIndustryUrl, query, body);
            return analysisHttpResponse(baseResult);
        } catch (Exception e) {
            return ExceptionUtil.formatException(e);
        }
    }

    /**
     * 获取设置的行业信息
     *
     * @return 结果
     */
    public BaseResult templateGetIndustry() {
        try {
            String templateGetIndustryUrl = API_WEI_XIN_SERVER_HOST + "cgi-bin/template/get_industry";
            Map<String, Object> query = new HashMap<>(2);
            query.put(ACCESS_TOKEN, getAccessToken());
            BaseResult baseResult = HttpUtil.getInstance().doGet(templateGetIndustryUrl, query);
            return analysisHttpResponse(baseResult);
        } catch (Exception e) {
            return ExceptionUtil.formatException(e);
        }
    }

    /**
     * 获取模板列表
     *
     * @return 结果
     */
    public BaseResult getAllPrivateMessageTemplate() {
        try {
            String getAllPrivateTemplateUrl = API_WEI_XIN_SERVER_HOST + "cgi-bin/template/get_all_private_template";
            Map<String, Object> query = new HashMap<>(2);
            query.put(ACCESS_TOKEN, getAccessToken());
            BaseResult baseResult = HttpUtil.getInstance().doGet(getAllPrivateTemplateUrl, query);
            return analysisHttpResponse(baseResult);
        } catch (Exception e) {
            return ExceptionUtil.formatException(e);
        }
    }

    /**
     * 删除消息模板
     *
     * @param templateId 模板ID
     * @return 结果
     */
    public BaseResult deleteTemplate(String templateId) {
        try {
            String removeTemplateUrl = API_WEI_XIN_SERVER_HOST + "cgi-bin/template/del_private_template";
            Map<String, Object> query = new HashMap<>(7);
            query.put(ACCESS_TOKEN, getAccessToken());
            Map<String, Object> body = new HashMap<>(7);
            body.put("template_id", templateId);
            BaseResult baseResult = HttpUtil.getInstance().doPost(removeTemplateUrl, query, body);
            return analysisHttpResponse(baseResult);
        } catch (Exception e) {
            return ExceptionUtil.formatException(e);
        }
    }

    /**
     * 模板消息发送
     *
     * @param templateMessage 模板
     * @return 结果
     */
    public BaseResult messageTemplateSend(TemplateMessage templateMessage) {

        try {
            Map<String, Object> query = new HashMap<>(2);
            query.put(ACCESS_TOKEN, getAccessToken());
            String messageTemplateSendUrl = API_WEI_XIN_SERVER_HOST + "cgi-bin/message/template/send";
            String body = JSON.toJSONString(templateMessage);
            BaseResult baseResult = HttpUtil.getInstance().doPost(messageTemplateSendUrl, query, body);
            return analysisHttpResponse(baseResult);
        } catch (Exception e) {
            return ExceptionUtil.formatException(e);
        }
    }

    /**
     * 获得授权认证连接 网页授权 第一步
     * URL:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html#0
     *
     * @param params
     * @param isSilence 是否静默授权 true：snsapi_base，false：snsapi_userinfo
     * @return 结果
     */
    public String getConnectOauth2Url(String params, Boolean isSilence) {
        try {
            LOGGER.info("state参数:{},长度为：{}", params, params.length());
            String redirectUri = chishipWxPubProperties.getServerDomain() + "/redirectUri/" + weiXinConfigModel.getAppKey();
            LOGGER.info("redirectUri:{}", redirectUri);
            String authorizeUrl = OPEN_WEI_XIN_SERVER_HOST
                    + "connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect";

            String url = authorizeUrl.replace("APPID", weiXinConfigModel.getAppKey())
                    .replace("REDIRECT_URI", URLEncoder.encode(redirectUri, BaseConstants.UTF8))
                    .replace("SCOPE", Boolean.TRUE.equals(isSilence) ? "snsapi_base" : "snsapi_userinfo");
            if (StringUtil.isNullOrEmpty(params)) {
                params = "";
            }
            url = url.replace("STATE", params);
            return url;
        } catch (Exception e) {
            throw new BusinessException("获得授权认证连接发生错误:" + e.getLocalizedMessage());
        }
    }

    /**
     * 通过code换取网页授权access_token 网页授权 第二步
     *
     * @param code
     * @return 结果
     */
    public BaseResult oauth2AccessToken(String code) {
        try {
            String accessTokenUrl = API_WEI_XIN_SERVER_HOST + "sns/oauth2/access_token";
            Map<String, Object> query = new HashMap<>(7);
            query.put("appid", weiXinConfigModel.getAppKey());
            query.put("secret", weiXinConfigModel.getAppSecret());
            query.put("code", code);
            query.put("grant_type", "authorization_code");
            BaseResult baseResult = HttpUtil.getInstance().doGet(accessTokenUrl, query);
            return analysisHttpResponse(baseResult);
        } catch (Exception e) {
            return ExceptionUtil.formatException(e);
        }
    }

    /**
     * 拉取用户信息(需scope为 snsapi_userinfo) 获得授权的用户信息 第四步
     *
     * @param openId
     * @param token
     * @return 结果
     */
    public BaseResult getAccessUserInfo(String token, String openId) {
        try {
            String getAccessUserUrl = API_WEI_XIN_SERVER_HOST + "sns/userinfo";
            Map<String, Object> query = new HashMap<>(7);
            query.put(ACCESS_TOKEN, token);
            query.put(OPEN_ID, openId);
            query.put("lang", "zh_CN");
            BaseResult baseResult = HttpUtil.getInstance().doGet(getAccessUserUrl, query);
            return analysisHttpResponse(baseResult);
        } catch (Exception e) {
            return ExceptionUtil.formatException(e);

        }
    }

    /**
     * 新增其他类型永久素材
     * URL:https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/Adding_Permanent_Assets.html
     *
     * @param inputStream           文件流
     * @param fileName              文件名称
     * @param title                 标题
     * @param introduction          描述
     * @param wxPubMaterialTypeEnum 素材类型
     * @return 结果
     * @throws Exception 异常
     */
    public BaseResult addMaterial(InputStream inputStream, String fileName, String title, String introduction,
                                  WxPubMaterialTypeEnum wxPubMaterialTypeEnum) {
        try {
            String urlString = API_WEI_XIN_SERVER_HOST + "cgi-bin/material/add_material?access_token="
                    + getAccessToken() + "&type=" + wxPubMaterialTypeEnum.getType();
            String result = null;
            URL url = new URL(urlString);
            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
            conn.setRequestMethod("POST");
            conn.setDoInput(true);
            conn.setDoOutput(true);
            conn.setUseCaches(false);
            // 设置请求头信息
            conn.setRequestProperty("Connection", "Keep-Alive");
            conn.setRequestProperty("Charset", BaseConstants.UTF8);
            // 设置边界
            String boundary = "----------" + System.currentTimeMillis();
            conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
            // 请求正文信息
            // 第一部分
            StringBuilder sb = new StringBuilder();
            sb.append("--");
            sb.append(boundary);
            sb.append("\r\n");
            sb.append("Content-Disposition: form-data;name=\"media\"; filename=\"" + fileName + "\"\r\n");
            sb.append("Content-Disposition: form-data;name=\"media\"; filename=\"" + fileName + "\"\r\n");
            sb.append("Content-Type:application/octet-stream\r\n\r\n");

            // 获得输出流
            OutputStream out = new DataOutputStream(conn.getOutputStream());
            if (WxPubMaterialTypeEnum.MATERIAL_TYPE_VIDEO.getType().equals(wxPubMaterialTypeEnum.getType())) {
                out.write(("--" + boundary + "\r\n").getBytes());
                out.write("Content-Disposition: form-data; name=\"description\";\r\n\r\n".getBytes());
                out.write(String.format("{\"title\":\"%s\", \"introduction\":\"%s\"}", title, introduction).getBytes());
                out.write(("\r\n--" + boundary + "--\r\n\r\n").getBytes());
            }
            // 输出表头
            out.write(sb.toString().getBytes(StandardCharsets.UTF_8));
            // 文件正文部分
            // 把文件以流的方式 推送道URL中
            DataInputStream din = new DataInputStream(inputStream);
            int bytes = 0;
            byte[] buffer = new byte[1024];
            while ((bytes = din.read(buffer)) != -1) {
                out.write(buffer, 0, bytes);
            }
            din.close();
            // 结尾部分
            byte[] foot = ("\r\n--" + boundary + "--\r\n").getBytes(StandardCharsets.UTF_8);
            out.write(foot);
            out.flush();
            out.close();
            if (BaseConstants.STATUS_OK == conn.getResponseCode()) {
                StringBuilder builder;
                BufferedReader reader = null;
                try {
                    builder = new StringBuilder();
                    reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
                    String lineString;
                    while ((lineString = reader.readLine()) != null) {
                        builder.append(lineString);
                    }
                    if (StringUtil.isNull(result)) {
                        return analysisHttpResponse(BaseResult.ok(builder.toString()));
                    } else {
                        return BaseResult.error("上传素材出现错误");

                    }
                } catch (IOException e) {
                    LOGGER.error("发送POST请求出现异常", e);
                    return BaseResult.error("发送POST请求出现异常！" + e.getLocalizedMessage());
                } finally {
                    if (reader != null) {
                        reader.close();
                    }
                }
            }
            return BaseResult.error("上传素材出现错误");
        } catch (Exception e) {
            return ExceptionUtil.formatException(e);
        }
    }

    /**
     * 获取永久素材
     *
     * @param mediaId 媒体ID
     * @return 结果
     */
    public BaseResult getMaterial(String mediaId) {
        try {
            String getMaterialUrl = API_WEI_XIN_SERVER_HOST + "cgi-bin/material/get_material";
            Map<String, Object> query = new HashMap<>(7);
            query.put(ACCESS_TOKEN, getAccessToken());
            Map<String, Object> body = new HashMap<>(7);
            body.put("media_id", mediaId);
            BaseResult baseResult = HttpUtil.getInstance().doPost(getMaterialUrl, query, body);
            return analysisHttpResponse(baseResult);
        } catch (Exception e) {
            return ExceptionUtil.formatException(e);
        }
    }

    /**
     * 素材列表
     * URL:https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/Get_materials_list.html
     *
     * @param pageIndex             第几页
     * @param wxPubMaterialTypeEnum 类型
     * @return 结果
     */
    public BaseResult getBatchMaterial(Integer pageIndex, WxPubMaterialTypeEnum wxPubMaterialTypeEnum) {
        try {
            String getBatchMaterialUrl = API_WEI_XIN_SERVER_HOST + "cgi-bin/material/batchget_material";
            Map<String, Object> query = new HashMap<>(7);
            query.put(ACCESS_TOKEN, getAccessToken());
            Map<String, Object> body = new HashMap<>(7);
            body.put("type", wxPubMaterialTypeEnum.getType());
            body.put("offset", String.valueOf((pageIndex - 1) * 20));
            body.put("count", String.valueOf(20));

            BaseResult baseResult = HttpUtil.getInstance().doPost(getBatchMaterialUrl, query, body);
            return analysisHttpResponse(baseResult);
        } catch (Exception e) {
            return ExceptionUtil.formatException(e);
        }
    }

    /**
     * 校验消息是否来自微信服务器
     * URL:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html
     *
     * @param response
     * @param request
     * @param token
     */
    public void validate(HttpServletRequest request, HttpServletResponse response, String token) {
        try {
            String signature = request.getParameter("signature");
            String timestamp = request.getParameter("timestamp");
            String nonce = request.getParameter("nonce");
            String echoStr = request.getParameter("echostr");
            String[] arrs = new String[]{token, timestamp, nonce};
            Arrays.sort(arrs);
            StringBuilder builder = new StringBuilder();
            for (String string : arrs) {
                builder.append(string);
            }
            if (Sha1Util.sha1Encode(builder.toString()).equals(signature)) {
                ResponseUtil.write(response, echoStr);
            } else {
                ResponseUtil.write(response, "校验失败");
            }
        } catch (Exception e) {
            try {
                ResponseUtil.write(response, "校验失败");
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        }
    }

    /**
     * 解析来自微信服务器消息
     *
     * @param request
     * @return Map<String, String>
     * @throws IOException
     * @throws DocumentException
     */
    public Map<String, String> dispose(HttpServletRequest request) throws IOException, DocumentException {
        Map<String, String> map = WxPubUtil.xmlToMap(request.getInputStream());
        map.put("appKey", this.weiXinConfigModel.getAppKey());
        map.put("appSecret", this.weiXinConfigModel.getAppSecret());
        return map;

    }

    /**
     * 回复文本消息
     * URL:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Passive_user_reply_message.html#%E5%9B%9E%E5%A4%8D%E6%96%87%E6%9C%AC%E6%B6%88%E6%81%AF
     *
     * @param fromUserName
     * @param toUserName
     * @param content
     * @return String
     */
    public String replyTextMessage(String fromUserName, String toUserName, String content) {
        ReplyTextMessage replyTextMessage = new ReplyTextMessage(fromUserName, toUserName, content);
        return replyTextMessage.toXml();
    }

    /**
     * 回复图片消息
     * URL:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Passive_user_reply_message.html#%E5%9B%9E%E5%A4%8D%E5%9B%BE%E7%89%87%E6%B6%88%E6%81%AF
     *
     * @param fromUserName
     * @param toUserName
     * @param mediaId
     * @return String
     */
    public String replyImageMessage(String fromUserName, String toUserName, String mediaId) {
        ReplyImageMessage replyImageMessage = new ReplyImageMessage(fromUserName, toUserName, mediaId);
        return replyImageMessage.toXml();
    }

    /**
     * 回复语音消息
     * URL:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Passive_user_reply_message.html#%E5%9B%9E%E5%A4%8D%E8%AF%AD%E9%9F%B3%E6%B6%88%E6%81%AF
     *
     * @param fromUserName
     * @param toUserName
     * @param mediaId
     * @return String
     */
    public String replyVoiceMessage(String fromUserName, String toUserName, String mediaId) {
        ReplyVoiceMessage replyVoiceMessage = new ReplyVoiceMessage(fromUserName, toUserName, mediaId);
        return replyVoiceMessage.toXml();
    }

    /**
     * 回复视频消息
     * URL:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Passive_user_reply_message.html#%E5%9B%9E%E5%A4%8D%E8%A7%86%E9%A2%91%E6%B6%88%E6%81%AF
     *
     * @param fromUserName
     * @param toUserName
     * @param mediaId
     * @param title
     * @param description
     * @return String
     */
    public String replyVideoMessage(String fromUserName, String toUserName, String mediaId, String title,
                                    String description) {
        ReplyVideoMessage replyVideoMessage = new ReplyVideoMessage(fromUserName, toUserName, mediaId, title,
                description);
        return replyVideoMessage.toXml();
    }

    /**
     * 回复图文消息
     * URL:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Passive_user_reply_message.html#%E5%9B%9E%E5%A4%8D%E5%9B%BE%E6%96%87%E6%B6%88%E6%81%AF
     *
     * @param fromUserName
     * @param toUserName
     * @param replyImageTextMessageItems
     * @return String
     */
    public String replyImageTextMessage(String fromUserName, String toUserName,
                                        List<ReplyImageTextMessageItem> replyImageTextMessageItems) {
        ReplyImageTextMessage replyImageTextMessage;
        if (replyImageTextMessageItems.size() > 8) {
            PrintUtil.console("图文消息超过了8条，系统自动截取前8条！");
            replyImageTextMessage = new ReplyImageTextMessage(fromUserName, toUserName,
                    replyImageTextMessageItems.subList(0, 8));

        } else {
            replyImageTextMessage = new ReplyImageTextMessage(fromUserName, toUserName, replyImageTextMessageItems);
        }
        return replyImageTextMessage.toXml();
    }

    public String getAccessToken() {
        if (StringUtil.isNullOrEmpty(accessToken)) {
            throw new BusinessException("token为空！请链式调用如下方法：config().token()获得Token");
        }
        return accessToken;
    }

    public WeiXinConfigModel getWeiXinConfigModel() {
        return weiXinConfigModel;
    }

    private BaseResult analysisHttpResponse(BaseResult baseResult) {
        Integer successStatus = 0;
        String errCode = "errcode";
        String errMsg = "errmsg";
        if (!baseResult.isSuccess()) {
            return BaseResult.error(baseResult);
        }
        try {
            JSONObject json = JSON.parseObject(StringUtil.getString(baseResult.getData()));
            if (json.containsKey(errCode)) {
                if (successStatus.equals(json.getInteger(errCode))) {
                    return BaseResult.ok(json);
                } else {
                    return BaseResult.error("【" + json.getInteger(errCode) + "】：" + json.getString(errMsg));
                }
            } else {
                return BaseResult.ok(json);
            }
        } catch (Exception e) {
            return baseResult;
        }
    }
}
